
local inspect = require "ddcl.inspect"
local tcp_channel = require "ddcl.tcp_channel"
local socket = require "ddcl.socket"
local service = require "ddcl.service"
local utils = require "ddcl.http.utils"
local log = require "ddcl.log"


local function make_response(M, c)
    local t = {
        version = "HTTP/1.1",
        is_resp = false,
    }
    t.add_cookie = function(value)
        t.cookies = t.cookies or {}
        table.insert(t.cookies, value)
    end
    t.add_header = function(k, v)
        t.header = t.header or {}
        if t.header[k] then
            if type(t.header[k]) == "string" then
                t.header[k] = { t.header[k] }
            end
            table.insert(t.header[k], v)
        else
            t.header[k] = v
        end
    end
    t.resp = function(data)
        if t.is_resp then return end
        if not c.channel then
            return
        end
        t.is_resp = true
        if not t.chunked then
            t.add_header("Content-Length", #data)
        end
        local header_str = utils.make_response_header_str(t)
        c.channel.send(header_str)
        c.channel.send(data)
    end
    return t
end

local function reset_response(response)
    response.code = 200
    response.code_msg = "ok"
    response.cookies = nil
    response.header = nil
    response.is_resp = false
end

local function attach_fd(M, fd, addr)
    local c = {
        fd = fd,
        addr = addr,
    }
    c.channel = tcp_channel(fd, {
        forward = M.forward,
        close_fn = function()
            c.close__()
        end
    })

    function c.close()
        if c.channel then
            c.channel.close()
        end
    end
    function c.close__()
        if M.close_fn then M.close_fn(M) end
        c.channel = nil
        c.fd = nil
    end
    local response = make_response(M, c)
    service.fork(function()
        while c.channel do
            local ret, request = pcall(utils.read_request_header,
                c.channel, M.max_header_len)
            if not ret then
                return
            end
            reset_response(response)
            if M.check_header_fn and not M.check_header_fn(c, request, response) then
                return c.close()
            end
            if not c.channel then return end
            request.body = utils.read_body(request.header, c.channel)
            if M.execute_fn then
                xpcall(M.execute_fn, log.log, c, request, response)
            end
            if not response.is_resp then
                response.code = 404
                response.code_msg = "ERROR"
                response.resp("execute not resped")
            end
            local keepalive = request.header["connection"]
            if not keepalive or string.lower(keepalive) ~= "keep-alive" then
                return c.close()
            end
        end
    end)
    return c
end

local function do_listen(M)
    local fd = socket.bind(M.host, M.port)
    assert(fd, string.format("listen error: %s:%d",M.host, M.port))
    M.fd = fd
    socket.listen(fd)
    socket.forward(fd, function(msg, nfd, addr)
        attach_fd(M, nfd, addr)
    end)
end


return function(opt)
    local M = {
        host = opt.host,
        port = opt.port,
        forward = opt.forward,
        max_header_len = opt.max_header_len or 65535,
        check_header_fn = opt.check_header_fn,
        execute_fn = opt.execute_fn,
    }

    function M.listen()
        do_listen(M)
    end

    function M.close()
        if M.fd then
            socket.close(M.fd)
            M.fd = nil
        end
    end

    return M
end
